#include <buola/gui/x11/auto.h>

#include <buola/os/cprocess.h>
#include <buola/gui/x11/cx11display.h>
#include <buola/app/ccmdline.h>
#include <buola/os/cenv.h>

#include <X11/Xatom.h>

namespace buola { namespace gui { namespace x11 {

/////////////////////////// init atoms ////////////////////////

CX11Display::SAtoms::SAtoms(CX11Display *pDisplay)
{
    const int lMax=200;

    Atom lAtomsReturn[lMax];
    Atom *lAtom[lMax];
    const char *lName[lMax];

    int i=0;

    lAtom[i]=&mMultiple;
    lName[i++]="MULTIPLE";

    lAtom[i]=&mTargets;
    lName[i++]="TARGETS";

    lAtom[i]=&mUTF8;
    lName[i++]="UTF8_STRING";

    lAtom[i]=&mCompound;
    lName[i++]="COMPOUND_TEXT";

    lAtom[i]=&mTextPlain;
    lName[i++]="text/plain";

    lAtom[i]=&mTextUrilist;
    lName[i++]="text/uri-list";

    lAtom[i]=&mProtocols;
    lName[i++]="WM_PROTOCOLS";

    lAtom[i]=&mHints;
    lName[i++]="WM_HINTS";

    lAtom[i]=&mWMCommand;
    lName[i++]="WM_COMMAND";

    lAtom[i]=&mWMClass;
    lName[i++]="WM_CLASS";

    lAtom[i]=&mName;
    lName[i++]="WM_NAME";

    lAtom[i]=&mIconName;
    lName[i++]="WM_ICON_NAME";

    lAtom[i]=&mWMClientMachine;
    lName[i++]="WM_CLIENT_MACHINE";

    lAtom[i]=&mLocaleName;
    lName[i++]="WM_LOCALE_NAME";

    lAtom[i]=&mMotifHints;
    lName[i++]="_MOTIF_WM_HINTS";

    lAtom[i]=&mState;
    lName[i++]="WM_STATE";

    lAtom[i]=&mDeleteWindow;
    lName[i++]="WM_DELETE_WINDOW";

    lAtom[i]=&mTakeFocus;
    lName[i++]="WM_TAKE_FOCUS";

    lAtom[i]=&mChangeState;
    lName[i++]="WM_CHANGE_STATE";

    lAtom[i]=&mClientLeader;
    lName[i++]="WM_CLIENT_LEADER";

    lAtom[i]=&mContextHelp;
    lName[i++]="_NET_WM_CONTEXT_HELP";

    lAtom[i]=&mNetSupported;
    lName[i++]="_NET_SUPPORTED";

    lAtom[i]=&mNetClientList;
    lName[i++]="_NET_CLIENT_LIST";

    lAtom[i]=&mNetClientListStacking;
    lName[i++]="_NET_CLIENT_LIST_STACKING";

    lAtom[i]=&mNetNumberOfDesktops;
    lName[i++]="_NET_NUMBER_OF_DESKTOPS";

    lAtom[i]=&mNetDesktopGeometry;
    lName[i++]="_NET_DESKTOP_GEOMETRY";

    lAtom[i]=&mNetDesktopViewport;
    lName[i++]="_NET_DESKTOP_VIEWPORT";

    lAtom[i]=&mNetCurrentDesktop;
    lName[i++]="_NET_CURRENT_DESKTOP";

    lAtom[i]=&mNetDesktopNames;
    lName[i++]="_NET_DESKTOP_NAMES";

    lAtom[i]=&mNetActiveWindow;
    lName[i++]="_NET_ACTIVE_WINDOW";

    lAtom[i]=&mNetWorkArea;
    lName[i++]="_NET_WORKAREA";

    lAtom[i]=&mNetSupportingWMCheck;
    lName[i++]="_NET_SUPPORTING_WM_CHECK";

    lAtom[i]=&mNetVirtualRoots;
    lName[i++]="_NET_VIRTUAL_ROOTS";

    lAtom[i]=&mNetDesktopLayout;
    lName[i++]="_NET_DESKTOP_LAYOUT";

    lAtom[i]=&mNetShowingDesktop;
    lName[i++]="_NET_SHOWING_DESKTOP";

    lAtom[i]=&mNetCloseWindow;
    lName[i++]="_NET_CLOSE_WINDOW";

    lAtom[i]=&mNetWMMoveResize;
    lName[i++]="_NET_WM_MOVERESIZE";

    lAtom[i]=&mNetWMName;
    lName[i++]="_NET_WM_NAME";

    lAtom[i]=&mNetWMIconName;
    lName[i++]="_NET_WM_ICON_NAME";

    lAtom[i]=&mNetWMVisibleName;
    lName[i++]="_NET_WM_VISIBLE_NAME";

    lAtom[i]=&mNetWMVisibleIconName;
    lName[i++]="_NET_WM_VISIBLE_ICON_NAME";

    lAtom[i]=&mNetWMDesktop;
    lName[i++]="_NET_WM_DESKTOP";

    lAtom[i]=&mNetWMWindowType;
    lName[i++]="_NET_WM_WINDOW_TYPE";

    lAtom[i]=&mNetWMState;
    lName[i++]="_NET_WM_STATE";

    lAtom[i]=&mNetWMStrut;
    lName[i++]="_NET_WM_STRUT";

    lAtom[i]=&mNetWMIconGeometry;
    lName[i++]="_NET_WM_ICON_GEOMETRY";

    lAtom[i]=&mNetWMIcon;
    lName[i++]="_NET_WM_ICON";

    lAtom[i]=&mNetWMPID;
    lName[i++]="_NET_WM_PID";

    lAtom[i]=&mNetWMHandledIcons;
    lName[i++]="_NET_WM_HANDLED_ICONS";

    lAtom[i]=&mNetWMUserTime;
    lName[i++]="_NET_WM_USER_TIME";

    lAtom[i]=&mNetFrameExtents;
    lName[i++]="_NET_FRAME_EXTENTS";

    lAtom[i]=&mNetWMPing;
    lName[i++]="_NET_WM_PING";

    lAtom[i]=&mNetWMSyncRequest;
    lName[i++]="_NET_WM_SYNC_REQUEST";

    lAtom[i]=&mNetWMSyncRequestCounter;
    lName[i++]="_NET_WM_SYNC_REQUEST_COUNTER";

    lAtom[i]=&mNetWMWindowTypeDesktop;
    lName[i++]="_NET_WM_WINDOW_TYPE_DESKTOP";

    lAtom[i]=&mNetWMWindowTypeDock;
    lName[i++]="_NET_WM_WINDOW_TYPE_DOCK";

    lAtom[i]=&mNetWMWindowTypeToolbar;
    lName[i++]="_NET_WM_WINDOW_TYPE_TOOLBAR";

    lAtom[i]=&mNetWMWindowTypeMenu;
    lName[i++]="_NET_WM_WINDOW_TYPE_MENU";

    lAtom[i]=&mNetWMWindowTypeUtility;
    lName[i++]="_NET_WM_WINDOW_TYPE_UTILITY";

    lAtom[i]=&mNetWMWindowTypeSplash;
    lName[i++]="_NET_WM_WINDOW_TYPE_SPLASH";

    lAtom[i]=&mNetWMWindowTypeDialog;
    lName[i++]="_NET_WM_WINDOW_TYPE_DIALOG";

    lAtom[i]=&mNetWMWindowTypeDropdownMenu;
    lName[i++]="_NET_WM_WINDOW_TYPE_DROPDOWN_MENU";

    lAtom[i]=&mNetWMWindowTypePopupMenu;
    lName[i++]="_NET_WM_WINDOW_TYPE_POPUP_MENU";

    lAtom[i]=&mNetWMWindowTypeTooltip;
    lName[i++]="_NET_WM_WINDOW_TYPE_TOOLTIP";

    lAtom[i]=&mNetWMWindowTypeNotification;
    lName[i++]="_NET_WM_WINDOW_TYPE_NOTIFICATION";

    lAtom[i]=&mNetWMWindowTypeCombo;
    lName[i++]="_NET_WM_WINDOW_TYPE_COMBO";

    lAtom[i]=&mNetWMWindowTypeDND;
    lName[i++]="_NET_WM_WINDOW_TYPE_DND";

    lAtom[i]=&mNetWMWindowTypeNormal;
    lName[i++]="_NET_WM_WINDOW_TYPE_NORMAL";

    lAtom[i]=&mNetWMWindowTypeKDEOverride;
    lName[i++]="_KDE_NET_WM_WINDOW_TYPE_OVERRIDE";

    lAtom[i]=&mNetWMStateModal;
    lName[i++]="_NET_WM_STATE_MODAL";

    lAtom[i]=&mNetWMStateSticky;
    lName[i++]="_NET_WM_STATE_STICKY";

    lAtom[i]=&mNetWMStateMaxVert;
    lName[i++]="_NET_WM_STATE_MAXIMIZED_VERT";

    lAtom[i]=&mNetWMStateMaxHorz;
    lName[i++]="_NET_WM_STATE_MAXIMIZED_HORZ";

    lAtom[i]=&mNetWMStateShaded;
    lName[i++]="_NET_WM_STATE_SHADED";

    lAtom[i]=&mNetWMStateSkipTaskbar;
    lName[i++]="_NET_WM_STATE_SKIP_TASKBAR";

    lAtom[i]=&mNetWMStateSkipPager;
    lName[i++]="_NET_WM_STATE_SKIP_PAGER";

    lAtom[i]=&mNetWMStateHidden;
    lName[i++]="_NET_WM_STATE_HIDDEN";

    lAtom[i]=&mNetWMStateFullScreen;
    lName[i++]="_NET_WM_STATE_FULLSCREEN";

    lAtom[i]=&mNetWMStateAbove;
    lName[i++]="_NET_WM_STATE_ABOVE";

    lAtom[i]=&mNetWMStateBelow;
    lName[i++]="_NET_WM_STATE_BELOW";

    lAtom[i]=&mNetWMStateDemandsAttention;
    lName[i++]="_NET_WM_STATE_DEMANDS_ATTENTION";

    lAtom[i]=&mNetWMWindowOpacity;
    lName[i++]="_NET_WM_WINDOW_OPACITY";

    lAtom[i]=&mNetStartupID;
    lName[i++]="_NET_STARTUP_ID";

    lAtom[i]=&mSupportingCMCheck;
    lName[i++]="_BUOLA_SUPPORTING_CM_CHECK";

    lAtom[i]=&mBuolaDockWindow;
    lName[i++]="_BUOLA_DOCK_WINDOW";

    lAtom[i]=&mBuolaThumbnail;
    lName[i++]="_BUOLA_THUMBNAIL";

    lAtom[i]=&mBuolaWMExtensions;
    lName[i++]="_BUOLA_WM_EXTENSIONS";

    lAtom[i]=&mBuolaCustomDeco;
    lName[i++]="_BUOLA_CUSTOM_DECO";

    lAtom[i]=&mDndAware;
    lName[i++]="XdndAware";

    lAtom[i]=&mDndTypeList;
    lName[i++]="XdndTypeList";

    lAtom[i]=&mDndActionList;
    lName[i++]="XdndActionList";

    lAtom[i]=&mDndActionDescription;
    lName[i++]="XdndActionDescription";

    lAtom[i]=&mDndProxy;
    lName[i++]="XdndProxy";

    lAtom[i]=&mDndEnter;
    lName[i++]="XdndEnter";

    lAtom[i]=&mDndPosition;
    lName[i++]="XdndPosition";

    lAtom[i]=&mDndStatus;
    lName[i++]="XdndStatus";

    lAtom[i]=&mDndLeave;
    lName[i++]="XdndLeave";

    lAtom[i]=&mDndDrop;
    lName[i++]="XdndDrop";

    lAtom[i]=&mDndFinished;
    lName[i++]="XdndFinished";

    lAtom[i]=&mDndActionCopy;
    lName[i++]="XdndActionCopy";

    lAtom[i]=&mDndActionMove;
    lName[i++]="XdndActionMove";

    lAtom[i]=&mDndActionLink;
    lName[i++]="XdndActionLink";

    lAtom[i]=&mDndActionAsk;
    lName[i++]="XdndActionAsk";

    lAtom[i]=&mDndActionPrivate;
    lName[i++]="XdndActionPrivate";

    lAtom[i]=&mClipboard;
    lName[i++]="CLIPBOARD";

    lAtom[i]=&mPrimary;
    lName[i++]="PRIMARY";

    lAtom[i]=&mSecondary;
    lName[i++]="SECONDARY";

    lAtom[i]=&mDndSelection;
    lName[i++]="XdndSelection";

    lAtom[i]=&mBuolaTransfer;
    lName[i++]="_BUOLA_TRANSFER";

    lAtom[i]=&mXEmbedInfo;
    lName[i++]="_XEMBED_INFO";

    lAtom[i]=&mXEmbed;
    lName[i++]="_XEMBED";

    lAtom[i]=&mStandard2x2;
    lName[i++]="Standard2x2";

    lAtom[i]=&mStandard4x4;
    lName[i++]="Standard4x4";

    lAtom[i]=&mStandard128x128;
    lName[i++]="Standard128x128";

    XInternAtoms(pDisplay->GetDisplay(),const_cast<char**>(lName),i,False,lAtomsReturn);

    for(int j=0;j<i;j++)
    {
        *lAtom[j]=lAtomsReturn[j];
    }
}


static CCmdLineFlag sOptionSync("sync",
        L"connect to server in synchronous mode (useful for debug)");

std::vector<CX11Display*> CX11Display::mDisplays;

CX11Display::CX11Display()
{
    mStatus=STATUS_UNKNOWN|STATUS_LOCAL;
    mDisplayName=CEnv::Get("DISPLAY",":0");
    mDisplay=nullptr;
}

CX11Display::CX11Display(const std::string &pName,const std::string &pCmd)
{
    mStatus=STATUS_UNKNOWN|STATUS_LOCAL|STATUS_HAVECMD;
    mDisplayName=pName;
    mDisplayCmd=pCmd;
    mDisplay=nullptr;
}

CX11Display::CX11Display(Display *pDisplay)
{
    mStatus=STATUS_OPEN|STATUS_RUNNING;
    mDisplay=pDisplay;
    mDisplayName=DisplayString(pDisplay);

    CalcDisplayInfo();
}

CX11Display::~CX11Display()
{
}

bool CX11Display::IsRunning()
{
    if(mStatus&STATUS_UNKNOWN) CalcStatus();

    return(mStatus&STATUS_RUNNING);
}

void CX11Display::CalcStatus()
{
    if(!(mStatus&STATUS_UNKNOWN)) return;

    mStatus&=~STATUS_UNKNOWN;

    mDisplay=XOpenDisplay(mDisplayName.c_str());

    if(mDisplay)
    {
        mStatus|=STATUS_RUNNING;
        XCloseDisplay(mDisplay);
    }
}

void CX11Display::CalcDisplayInfo()
{
    mConnectionNumber=ConnectionNumber(mDisplay);
    mScreenNumber=DefaultScreen(mDisplay);
    mScreen=DefaultScreenOfDisplay(mDisplay);
    mVisual=DefaultVisualOfScreen(mScreen);
    mDepth=DefaultDepthOfScreen(mScreen);
    mColormap=DefaultColormapOfScreen(mScreen);
    mDefaultRoot=DefaultRootWindow(mDisplay);
    mAtoms=new SAtoms(this);
}

void CX11Display::Open()
{
    if(mStatus&STATUS_OPEN) return;

    mDisplay=XOpenDisplay(mDisplayName.c_str());

    if(mDisplay)
    {
        if(cmd_line().IsSet(sOptionSync))
        {
            XSynchronize(mDisplay,True);
        }

        CalcDisplayInfo();
        mStatus|=STATUS_OPEN|STATUS_RUNNING;
        mStatus&=~STATUS_UNKNOWN;
    }
    else
    {
        mStatus&=~(STATUS_OPEN|STATUS_RUNNING|STATUS_UNKNOWN);

        throw XInternal(I18N("Can't open display"));
    }
}

void CX11Display::Close()
{
    if(!(mStatus&STATUS_OPEN)) return;

    XCloseDisplay(mDisplay);
    mStatus&=~STATUS_OPEN;
}

void CX11Display::Launch()
{
    if(IsRunning()) return;

    CProcess lProcess;

    lProcess.SetCmd(mDisplayCmd);

    lProcess.Launch();
}

void CX11Display::StartIM()
{
    msg_info() << "starting input method!!!! " << (void*)mDisplay << "\n";
    XSetLocaleModifiers("");
    mXIC=nullptr;
    mXIM=XOpenIM(mDisplay,nullptr,nullptr,nullptr);
    XIMStyles *lSupStyles=nullptr;
    msg_info() << (void*) mXIM << "\n";
    if(!mXIM)
    {
        msg_warn() << "Can't get input method\n";
        return;
    }
    if(XGetIMValues(mXIM,XNQueryInputStyle,&lSupStyles,nullptr)!=nullptr)
    {
        XCloseIM(mXIM);
        msg_warn() << "Can't get any input style\n";
        return;
    }

    //Try to find the style we want
    XIMStyle lStyle=XIMPreeditNothing|XIMStatusNothing;
    bool lFound=false;

    for(int i=0;i<lSupStyles->count_styles;i++)
    {
        if(lSupStyles->supported_styles[i]==lStyle)
        {
            lFound=true;
            break;
        }
    }

    XFree(lSupStyles);

    if(!lFound)
    {
        XCloseIM(mXIM);
        msg_warn() << "Can't get a good input style\n";
        return;
    }

    mXIC=XCreateIC(mXIM,XNInputStyle,lStyle,
            XNClientWindow,DefaultRootWindow(mDisplay),nullptr);

    if(!mXIC)
    {
        XCloseIM(mXIM);
        msg_warn() << "Can't create input context\n";
        return;
    }

    XSetICFocus(mXIC);
}

/////////////////////////////////////////////
// X atoms, properties and client messages
//

Atom CX11Display::GetAtom(const std::wstring &pAtom,bool pOnlyIfExists)
{
    return XInternAtom(mDisplay,encode(pAtom,COD_ASCII).c_str(),pOnlyIfExists);
}

std::wstring CX11Display::GetAtomName(Atom pAtom)
{
    char *lName=XGetAtomName(mDisplay,pAtom);
    std::wstring lReturn=utf32(lName);

    XFree(lName);
    return lReturn;
}

bool CX11Display::SetWindowProperty(Window pW,Atom pP,Atom pT,int pFormat,
                int pCount,const void *pData)
{
    int lStatus=XChangeProperty(mDisplay,pW,pP,pT,
                pFormat,PropModeReplace,const_cast<unsigned char*>((const unsigned char*)pData),pCount);

    return (lStatus==Success);
}

int CX11Display::GetWindowProperty(Window pW,Atom pP,Atom pT,void **pData,int pMax)
{
    Atom lRealType;
    int lFormat;
    unsigned long lNItems,lExtraLen;

    int lStatus=XGetWindowProperty(mDisplay,pW,pP,0,
                pMax,False,pT,&lRealType,&lFormat,&lNItems,
                &lExtraLen,(unsigned char**)pData);

    if(lStatus != Success || (*pData)==nullptr) return -1;

    return lNItems;
}

bool CX11Display::SetWindowProperty(Window pW,Atom pP,Atom pT,
                                const std::wstring &pString)
{
    std::string lString;

    if(pT==XA_STRING)
    {
        lString=encode(pString,COD_ISO8859_1);
    }
    else if(pT==Atoms().mCompound)
    {
        lString=encode(pString,COD_ISO8859_1);
    }
    else //if(pT==Atoms().mUTF8)
    {
        lString=utf8(pString);
    }

    return SetWindowProperty(pW,pP,pT,lString);
}

bool CX11Display::GetWindowProperty(Window pW,Atom pP,Atom pT,std::wstring &pString)
{
    std::string lString;

    if(!GetWindowProperty(pW,pP,pT,lString))
    {
        pString.clear();
        return false;
    }

    if(pT==XA_STRING)
    {
        pString=decode(lString,COD_ISO8859_1);
    }
    else if(pT==Atoms().mCompound)
    {
        pString=decode(lString,COD_ISO8859_1);
    }
    else
    {
        pString=utf32(lString);
    }

    return true;
}

bool CX11Display::SetWindowProperty(Window pW,Atom pP,Atom pT,
                                const std::string &pString)
{
    return SetWindowProperty(pW,pP,pT,8,pString.length(),const_cast<char*>(pString.c_str()));
}

bool CX11Display::GetWindowProperty(Window pW,Atom pP,Atom pT,std::string &pString)
{
    char *lData;
    char **lPData=&lData;

    if(GetWindowProperty(pW,pP,pT,(void**)lPData)<=0)
    {
        pString.clear();
        return false;
    }

    pString=lData;

    XFree(lData);

    return true;
}

bool CX11Display::SetWindowProperty(Window pW,Atom pP,const std::vector<Atom> &pVector)
{
    return SetWindowProperty(pW,pP,XA_ATOM,32,pVector.size(),pVector.data());
}

bool CX11Display::GetWindowProperty(Window pW,Atom pP,std::vector<Atom> &pVector)
{
    Atom *lAtoms;
    Atom **lPAtoms=&lAtoms;
    int lCount=GetWindowProperty(pW,pP,XA_ATOM,(void**)lPAtoms,1024);

    if(lCount<0)
    {
        pVector.clear();
        return false;
    }

    pVector.resize(lCount);
    std::copy(lAtoms,lAtoms+lCount,pVector.begin());

    XFree(lAtoms);

    return true;
}

bool CX11Display::SetWindowProperty(Window pW,Atom pP,int pValue)
{
    return SetWindowProperty(pW,pP,XA_CARDINAL,32,1,&pValue);
}

bool CX11Display::SetWindowPropertyAsAtom(Window pW,Atom pP,Atom pValue)
{
    return SetWindowProperty(pW,pP,XA_ATOM,32,1,&pValue);
}

bool CX11Display::GetWindowProperty(Window pW,Atom pP,int *pDest)
{
    unsigned char *lDest;
    unsigned char **lPDest=&lDest;

    if(GetWindowProperty(pW,pP,XA_INTEGER,(void**)lPDest,1)<=0)
        return false;

    *pDest=(int)(*((long int*)lDest));

    XFree(lDest);

    return true;
}

bool CX11Display::HasWindowProperty(Window pW,Atom pP)
{
    unsigned char *lData;
    unsigned long lAfterReturn;
    Atom lActualType;
    int lActualFormat;
    unsigned long lNumItems;
    int lStatus=XGetWindowProperty(mDisplay,pW,pP,0,1,false,
        AnyPropertyType,&lActualType,&lActualFormat,&lNumItems,&lAfterReturn,&lData);

    return (lStatus==Success&&lActualType!=None);
}

void CX11Display::SendClientMessage(Window pSrc,Window pDest,Atom pAtom,
            int l1,int l2,int l3,int l4,int l5,long pMask,bool pPropagate)
{
    XEvent lXE;

    lXE.xclient.type=ClientMessage;
    lXE.xclient.window=pSrc;
    lXE.xclient.message_type=pAtom;
    lXE.xclient.format=32;
    lXE.xclient.data.l[0]=l1;
    lXE.xclient.data.l[1]=l2;
    lXE.xclient.data.l[2]=l3;
    lXE.xclient.data.l[3]=l4;
    lXE.xclient.data.l[4]=l5;

    XSendEvent(mDisplay,pDest,pPropagate,pMask,&lXE);
}

//////////////////////
// static functions
//

CX11Display *CX11Display::AddDisplay(const std::string &pName,const std::string &pCmd)
{
    CX11Display *lDisplay=new CX11Display(pName,pCmd);
    mDisplays.push_back(lDisplay);
    return lDisplay;
}

CX11Display *CX11Display::AddDisplay(Display *pDisplay)
{
    CX11Display *lDisplay=new CX11Display(pDisplay);
    mDisplays.push_back(lDisplay);
    return lDisplay;
}

CX11Display *CX11Display::AddDefaultDisplay()
{
    CX11Display *lDisplay=new CX11Display();
    mDisplays.push_back(lDisplay);
    return lDisplay;
}

/*namespace x11*/ } /*namespace gui*/ } /*namespace buola*/ }
